//-----------------------------------------------------------------------
// <copyright file="GetInfo.cs" company="Microsoft">
// Copyright (c) Microsoft Corporation.  All rights reserved.
// THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY 
// KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
// </copyright>
// <author>Andy Reeves</author>
// <email>andyr</email>
// <date>2004-03-25</date>
// <summary>Returns Info on a folder</summary>
//-----------------------------------------------------------------------

namespace Microsoft.Sdc.Tasks.Folder
{
    using System;
    using System.Xml;
    using System.Globalization;
    using System.IO;
    using System.Collections;

    using Microsoft.Build.Framework;
    using System.Security.Principal;

    /// <summary>
    /// Returns information about the folder specified
    /// </summary>
    /// <remarks>
    /// <code><![CDATA[<Folder.GetInfo ConnectAsDomain="connectAsDomain" ConnectAsPassword="connectAsPassword" ConnectAsUser="connectAsUser" MaximumDepth="maximumDepth" NewestDirectoryPathCreated="newestDirectoryPathCreated" NewestDirectoryPathCreationTime="newestDirectoryPathCreationTime" NewestDirectoryPathLastWriteTime="newestDirectoryPathLastWriteTime" NewestDirectoryPathModified="newestDirectoryPathModified" NewestFileCreated="newestFileCreated" NewestFileCreationTime="newestFileCreationTime" NewestFileLastWriteTime="newestFileLastWriteTime" NewestFileModified="newestFileModified" OldestDirectoryPathCreated="oldestDirectoryPathCreated" OldestDirectoryPathCreationTime="oldestDirectoryPathCreationTime" OldestDirectoryPathLastWriteTime="oldestDirectoryPathLastWriteTime" OldestDirectoryPathModified="oldestDirectoryPathModified" OldestFileCreated="oldestFileCreated" OldestFileCreationTime="oldestFileCreationTime" OldestFileLastWriteTime="oldestFileLastWriteTime" OldestFileModified="oldestFileModified" Path="path" Size="size" />]]></code>
    /// <para>where:</para>
    /// <para><i>connectAsDomain</i></para>
    /// <para>
    /// The domain for the impersonated user when a user is to be impersonated.
    /// </para>
    /// <para><i>connectAsPassword</i></para>
    /// <para>
    /// The password for the impersonated user when a user is to be impersonated.
    /// </para>
    /// <para><i>connectAsUser</i></para>
    /// <para>
    /// The name of the impersonated user when a user is to be impersonated.
    /// </para>
    /// <para><i>maximumDepth</i></para>
    /// <para>
    /// The maximum depth of the directory tree to be examined. There is no maximum depth by default.
    /// </para>
    /// <para><i>newestDirectoryPathCreated (Output)</i></para>
    /// <para>
    /// The full path to the directory with the newest creation date and time.
    /// </para>
    /// <para><i>newestDirectoryPathCreationTime (Output)</i></para>
    /// <para>
    /// The date and time of the directory with the newest creation date and time.
    /// </para>
    /// <para><i>newestDirectoryPathLastWriteTime (Output)</i></para>
    /// <para>
    /// The date and time of the directory with the newest modification date and time.
    /// </para>
    /// <para><i>newestDirectoryPathModified (Output)</i></para>
    /// <para>
    /// The full path to the directory with the newest modification date and time.
    /// </para>
    /// <para><i>newestFileCreated (Output)</i></para>
    /// <para>
    /// The full path to the file with the newest creation date and time.
    /// </para>
    /// <para><i>newestFileCreationTime (Output)</i></para>
    /// <para>
    /// The date and time of the file with the newest creation date and time.
    /// </para>
    /// <para><i>newestFileLastWriteTime (Output)</i></para>
    /// <para>
    /// The date and time of the file with the newest modification date and time.
    /// </para>
    /// <para><i>newestFileModified (Output)</i></para>
    /// <para>
    /// The full path to the file with the newest modification date and time.
    /// </para>
    /// <para><i>oldestDirectoryPathCreated (Output)</i></para>
    /// <para>
    /// The full path to the directory with the oldest creation date and time.
    /// </para>
    /// <para><i>oldestDirectoryPathCreationTime (Output)</i></para>
    /// <para>
    /// The date and time of the directory with the oldest creation date and time.
    /// </para>
    /// <para><i>oldestDirectoryPathLastWriteTime (Output)</i></para>
    /// <para>
    /// The date and time of the directory with the oldest modification date and time.
    /// </para>
    /// <para><i>oldestDirectoryPathModified (Output)</i></para>
    /// <para>
    /// The full path to the directory with the oldest modification date and time.
    /// </para>
    /// <para><i>oldestFileCreated (Output)</i></para>
    /// <para>
    /// The full path to the file with the oldest creation date and time.
    /// </para>
    /// <para><i>oldestFileCreationTime (Output)</i></para>
    /// <para>
    /// The date and time of the file with the oldest creation date and time.
    /// </para>
    /// <para><i>oldestFileLastWriteTime (Output)</i></para>
    /// <para>
    /// The date and time of the file with the oldest modification date and time.
    /// </para>
    /// <para><i>oldestFileModified (Output)</i></para>
    /// <para>
    /// The full path to the file with the oldest modification date and time.
    /// </para>
    /// <para><i>path (Required)</i></para>
    /// <para>
    /// The full path to the folder to return information for.
    /// </para>
    /// <para><i>size (Output)</i></para>
    /// <para>
    /// The total size of all files that were examined.
    /// </para>
    /// </remarks>
    /// <example>
    /// <code><![CDATA[
    /// <Project>
    ///     <Target Name="Test">
    ///         <Folder.GetInfo
    ///             Path="C:\SourceCopy">
    ///           <Output TaskParameter="Size" PropertyName="ActualSize" />   
    ///         </Folder.GetInfo>
    ///         <Message Text="The size is $(ActualSize)" />
    ///     </Target>
    /// </Project>
    /// ]]></code>    
    /// </example>
    public class GetInfo : TaskBase
    {
        /// <summary>
        /// The domain for the impersonated user when a user is to be impersonated.
        /// </summary>
        private string connectAsDomain;

        /// <summary>
        /// The password for the impersonated user when a user is to be impersonated.
        /// </summary>
        private string connectAsPassword;

        /// <summary>
        /// The name of the impersonated user when a user is to be impersonated.
        /// </summary>
        private string connectAsUser;

        /// <summary>
        /// The impersonation context when a user is to be impersonated.
        /// </summary>
        private WindowsImpersonationContext impersonationContext;

        /// <summary>
        /// When set, impersonates a user during the action of the task.
        /// </summary>
        private bool impersonateUser;

        /// <summary>
        /// The maximum depth of the directory tree to be examined.
        /// </summary>
        private int maximumDepth;

        /// <summary>
        /// When set, applies the maximum depth of the directory tree to be examined.
        /// </summary>
        private bool maximumDepthSpecified;

        /// <summary>
        /// The full path to the directory with the newest creation date and time.
        /// </summary>
        private string newestDirectoryPathCreated;

        /// <summary>
        /// The date and time of the directory with the newest creation date and time.
        /// </summary>
        private DateTime newestDirectoryPathCreationTime;

        /// <summary>
        /// The date and time of the directory with the newest modification date and time.
        /// </summary>
        private DateTime newestDirectoryPathLastWriteTime;

        /// <summary>
        /// The full path to the directory with the newest modification date and time.
        /// </summary>
        private string newestDirectoryPathModified;

        /// <summary>
        /// The full path to the file with the newest creation date and time.
        /// </summary>
        private string newestFileCreated;

        /// <summary>
        /// The date and time of the file with the newest creation date and time.
        /// </summary>
        private DateTime newestFileCreationTime;

        /// <summary>
        /// The date and time of the file with the newest modification date and time.
        /// </summary>
        private DateTime newestFileLastWriteTime;

        /// <summary>
        /// The full path to the file with the newest modification date and time.
        /// </summary>
        private string newestFileModified;

        /// <summary>
        /// The full path to the directory with the oldest creation date and time.
        /// </summary>
        private string oldestDirectoryPathCreated;

        /// <summary>
        /// The date and time of the directory with the oldest creation date and time.
        /// </summary>
        private DateTime oldestDirectoryPathCreationTime;

        /// <summary>
        /// The date and time of the directory with the oldest modification date and time.
        /// </summary>
        private DateTime oldestDirectoryPathLastWriteTime;

        /// <summary>
        /// The full path to the directory with the oldest modification date and time.
        /// </summary>
        private string oldestDirectoryPathModified;

        /// <summary>
        /// The full path to the file with the oldest creation date and time.
        /// </summary>
        private string oldestFileCreated;

        /// <summary>
        /// The date and time of the file with the oldest creation date and time.
        /// </summary>
        private DateTime oldestFileCreationTime;

        /// <summary>
        /// The date and time of the file with the oldest modification date and time.
        /// </summary>
        private DateTime oldestFileLastWriteTime;

        /// <summary>
        /// The full path to the file with the oldest modification date and time.
        /// </summary>
        private string oldestFileModified;

        /// <summary>
        /// The full path to the folder to return information for.
        /// </summary>
        private string path;

        /// <summary>
        /// The full path including the drive letter to the folder to return information for
        /// </summary>
        private string fullPathIncludingDriveLetter;

        /// <summary>
        /// The total size of all files that were examined.
        /// </summary>
        private long size;

        /// <summary>
        /// Initializes a new instance of the GetInfo class.
        /// </summary>
        public GetInfo()
        {
        }

        /// <summary>
        /// Gets or sets the domain for the impersonated user when a user is to be impersonated.
        /// </summary>
        public string ConnectAsDomain
        {
            get { return (this.connectAsDomain == null ? String.Empty : this.connectAsDomain); }
            set { this.connectAsDomain = value; }
        }

        /// <summary>
        /// Gets or sets the password for the impersonated user when a user is to be impersonated.
        /// </summary>
        public string ConnectAsPassword
        {
            get { return (this.connectAsPassword == null ? String.Empty : this.connectAsPassword); }
            set { this.connectAsPassword = value; }
        }

        /// <summary>
        /// Gets or sets the name of the impersonated user when a user is to be impersonated.
        /// </summary>
        public string ConnectAsUser
        {
            get { return (this.connectAsUser == null ? String.Empty : this.connectAsUser); }
            set { this.connectAsUser = value; }
        }

        /// <summary>
        /// Gets or sets the maximum depth of the directory tree to be examined.
        /// </summary>
        /// <remarks>
        /// There is no maximum depth by default.
        /// </remarks>
        public int MaximumDepth
        {
            get
            {
                return this.maximumDepth;
            }

            set
            {
                this.maximumDepth = value;
                this.maximumDepthSpecified = true;
            }
        }

        /// <summary>
        /// Gets the full path to the file with the newest creation date and time.
        /// </summary>
        [Output]
        public string NewestFileCreated
        {
            get { return (this.newestFileCreated == null ? String.Empty : this.newestFileCreated); }
        }

        /// <summary>
        /// Gets the date and time of the file with the newest creation date and time.
        /// </summary>
        [Output]
        public DateTime NewestFileCreationTime
        {
            get { return this.newestFileCreationTime; }
        }

        /// <summary>
        /// Gets the date and time of the file with the newest modification date and time.
        /// </summary>
        [Output]
        public DateTime NewestFileLastWriteTime
        {
            get { return this.newestFileLastWriteTime; }
        }

        /// <summary>
        /// Gets the full path to the file with the newest modification date and time.
        /// </summary>
        [Output]
        public string NewestFileModified
        {
            get { return (this.newestFileModified == null ? String.Empty : this.newestFileModified); }
        }

        /// <summary>
        /// The full path to the directory with the newest creation date and time.
        /// </summary>
        [Output]
        public string NewestDirectoryPathCreated
        {
            get { return (this.newestDirectoryPathCreated == null ? String.Empty : this.newestDirectoryPathCreated); }
        }

        /// <summary>
        /// The date and time of the directory with the newest creation date and time.
        /// </summary>
        [Output]
        public DateTime NewestDirectoryPathCreationTime
        {
            get { return this.newestDirectoryPathCreationTime; }
        }

        /// <summary>
        /// The date and time of the directory with the newest modification date and time.
        /// </summary>
        [Output]
        public DateTime NewestDirectoryPathLastWriteTime
        {
            get { return this.newestDirectoryPathLastWriteTime; }
        }

        /// <summary>
        /// The full path to the directory with the newest modification date and time.
        /// </summary>
        [Output]
        public string NewestDirectoryPathModified
        {
            get { return (this.newestDirectoryPathModified == null ? String.Empty : this.newestDirectoryPathModified); }
        }

        /// <summary>
        /// Gets the full path to the file with the oldest creation date and time.
        /// </summary>
        [Output]
        public string OldestFileCreated
        {
            get { return (this.oldestFileCreated == null ? String.Empty : this.oldestFileCreated); }
        }

        /// <summary>
        /// Gets the date and time of the file with the oldest creation date and time.
        /// </summary>
        [Output]
        public DateTime OldestFileCreationTime
        {
            get { return this.oldestFileCreationTime; }
        }

        /// <summary>
        /// Gets the date and time of the file with the oldest modification date and time.
        /// </summary>
        [Output]
        public DateTime OldestFileLastWriteTime
        {
            get { return this.oldestFileLastWriteTime; }
        }

        /// <summary>
        /// Gets the full path to the file with the oldest modification date and time.
        /// </summary>
        [Output]
        public string OldestFileModified
        {
            get { return (this.oldestFileModified == null ? String.Empty : this.oldestFileModified); }
        }

        /// <summary>
        /// The full path to the directory with the oldest creation date and time.
        /// </summary>
        [Output]
        public string OldestDirectoryPathCreated
        {
            get { return (this.oldestDirectoryPathCreated == null ? String.Empty : this.oldestDirectoryPathCreated); }
        }

        /// <summary>
        /// The date and time of the directory with the oldest creation date and time.
        /// </summary>
        [Output]
        public DateTime OldestDirectoryPathCreationTime
        {
            get { return this.oldestDirectoryPathCreationTime; }
        }

        /// <summary>
        /// The date and time of the directory with the oldest modification date and time.
        /// </summary>
        [Output]
        public DateTime OldestDirectoryPathLastWriteTime
        {
            get { return this.oldestDirectoryPathLastWriteTime; }
        }

        /// <summary>
        /// The full path to the directory with the oldest modification date and time.
        /// </summary>
        [Output]
        public string OldestDirectoryPathModified
        {
            get { return (this.oldestDirectoryPathModified == null ? String.Empty : this.oldestDirectoryPathModified); }
        }

        /// <summary>
        /// Gets or sets the full path to the directory. 
        /// </summary>
        [Required]
        public string Path
        {
            get { return (this.path == null ? String.Empty : this.path); }
            set { this.path = value; }
        }

        /// <summary>
        /// Gets the total size of all files that were examined.
        /// </summary>
        [Output]
        public long Size
        {
            get { return this.size; }
        }

        /// <summary>
        /// Performs the action of this task.
        /// </summary>
        protected override void InternalExecute()
        {
            Log.LogMessage(MessageImportance.Normal, "Processing {0}", this.Path);
            this.size = 0;
            this.newestDirectoryPathCreationTime = DateTime.MinValue;
            this.newestDirectoryPathLastWriteTime = DateTime.MinValue;
            this.newestFileCreationTime = DateTime.MinValue;
            this.newestFileLastWriteTime = DateTime.MinValue;
            this.oldestDirectoryPathCreationTime = DateTime.Now.ToUniversalTime();
            this.oldestDirectoryPathLastWriteTime = DateTime.Now.ToUniversalTime();
            this.oldestFileCreationTime = DateTime.Now.ToUniversalTime();
            this.oldestFileLastWriteTime = DateTime.Now.ToUniversalTime();

            DirectoryInfo directoryInfo = new DirectoryInfo(this.Path);
            this.fullPathIncludingDriveLetter = directoryInfo.FullName;

            this.ImpersonateIfRequired();
            this.ProcessDirectory(directoryInfo, 0);
            this.ImpersonateUndo();
            this.size = this.size / 1024;
        }

        /// <summary>
        /// Impersonates a user during the action of the task.
        /// </summary>
        /// <param name="userName">The name of the user to be impersonated.</param>
        /// <param name="domain">The domain for the user to be impersonated.</param>
        /// <param name="password">The password for the user to be impersonated.</param>
        /// <returns>The impersonation context for the user.</returns>
        private WindowsImpersonationContext ImpersonateUser(string userName, string domain, string password)
        {
            WindowsImpersonationContext impersonationContext;
            WindowsIdentity tempWindowsIdentity;
            IntPtr token = IntPtr.Zero;
            IntPtr tokenDuplicate = IntPtr.Zero;

            if (NativeMethods.LogonUser(userName, domain, password, NativeMethods.LOGON32_LOGON_INTERACTIVE, NativeMethods.LOGON32_PROVIDER_DEFAULT, out token))
            {
                if (NativeMethods.DuplicateToken(token, 2, ref tokenDuplicate) != 0)
                {
                    tempWindowsIdentity = new WindowsIdentity(tokenDuplicate);
                    impersonationContext = tempWindowsIdentity.Impersonate();

                    return impersonationContext;
                }
                else
                {
                    return null;
                }
            }
            else
            {
                return null;
            }
        }

        /// <summary>
        /// Impersonates a user when impersonation has been configured.
        /// </summary>
        private void ImpersonateIfRequired()
        {
            if (this.ConnectAsUser.Length > 0)
            {
                Log.LogMessage(MessageImportance.Low, "Impersonating {0}", this.ConnectAsUser);
                this.impersonationContext = this.ImpersonateUser(this.ConnectAsUser, this.ConnectAsDomain, this.ConnectAsPassword);
                if (this.impersonationContext == null)
                {
                    throw new TaskException("Folder.GetInfo.ImpersonateFailed", this.ConnectAsUser);
                }

                this.impersonateUser = true;
            }
        }

        /// <summary>
        /// Stops impersonation of a user.
        /// </summary>
        private void ImpersonateUndo()
        {
            if (this.impersonateUser)
            {
                Log.LogMessage(MessageImportance.Low, "Impersonation of {0} undo", this.ConnectAsUser);
                this.impersonationContext.Undo();
            }
        }

        /// <summary>
        /// Examines a directory to determine if it is newer or older than any other directories.
        /// </summary>
        /// <param name="directoryInfo">The directory to be examined.</param>
        private void UpdatePath(DirectoryInfo directoryInfo)
        {
            // don't update for the start path
            if (directoryInfo.FullName.Equals(this.fullPathIncludingDriveLetter, StringComparison.InvariantCultureIgnoreCase))
            {
                return;
            }

            if (directoryInfo.CreationTimeUtc < this.oldestDirectoryPathCreationTime)
            {
                this.oldestDirectoryPathCreationTime = directoryInfo.CreationTimeUtc;
                this.oldestDirectoryPathCreated = directoryInfo.FullName;
            }

            if (directoryInfo.CreationTimeUtc > this.newestDirectoryPathCreationTime)
            {
                this.newestDirectoryPathCreationTime = directoryInfo.CreationTimeUtc;
                this.newestDirectoryPathCreated = directoryInfo.FullName;
            }

            if (directoryInfo.LastWriteTimeUtc < this.oldestDirectoryPathLastWriteTime)
            {
                this.oldestDirectoryPathLastWriteTime = directoryInfo.LastWriteTimeUtc;
                this.oldestDirectoryPathModified = directoryInfo.FullName;
            }

            if (directoryInfo.LastWriteTimeUtc > this.newestDirectoryPathLastWriteTime)
            {
                this.newestDirectoryPathLastWriteTime = directoryInfo.LastWriteTimeUtc;
                this.newestDirectoryPathModified = directoryInfo.FullName;
            }
        }

        /// <summary>
        /// Examines a file to determine if it is newer or older than any other files.
        /// </summary>
        /// <param name="fileInfo">The file to be examined.</param>
        private void UpdateFile(FileInfo fileInfo)
        {
            if (fileInfo.CreationTimeUtc < this.oldestFileCreationTime)
            {
                this.oldestFileCreationTime = fileInfo.CreationTimeUtc;
                this.oldestFileCreated = fileInfo.FullName;
            }

            if (fileInfo.CreationTimeUtc > this.newestFileCreationTime)
            {
                this.newestFileCreationTime = fileInfo.CreationTimeUtc;
                this.newestFileCreated = fileInfo.FullName;
            }

            if (fileInfo.LastWriteTimeUtc < this.oldestFileLastWriteTime)
            {
                this.oldestFileLastWriteTime = fileInfo.LastWriteTimeUtc;
                this.oldestFileModified = fileInfo.FullName;
            }

            if (fileInfo.LastWriteTimeUtc > this.newestFileLastWriteTime)
            {
                this.newestFileLastWriteTime = fileInfo.LastWriteTimeUtc;
                this.newestFileModified = fileInfo.FullName;
            }
        }

        /// <summary>
        /// Recursively examines directories and files to determine the newest and oldest directories and files.
        /// </summary>
        /// <param name="directoryInfo">The directory to examine.</param>
        /// <param name="depth">The current depth within the directory tree.</param>
        private void ProcessDirectory(DirectoryInfo directoryInfo, int depth)
        {
            Log.LogMessage(MessageImportance.Low, "Processing {0}", directoryInfo.FullName);

            this.UpdatePath(directoryInfo);
            
            FileInfo[] fileInfos = directoryInfo.GetFiles();

            foreach (FileInfo fi in fileInfos)
            {
                try
                {
                    this.size += fi.Length;
                    this.UpdateFile(fi);
                }
                catch (DirectoryNotFoundException ex)
                {
                    Log.LogWarningFromException(ex);
                }
            }

            // Examine the subdirectories when the maximum depth has not been exceeded
            int subDirectoryDepth = depth + 1;
            if (this.maximumDepthSpecified == false || subDirectoryDepth <= this.maximumDepth)
            {
                DirectoryInfo[] directoryInfos = directoryInfo.GetDirectories();
                foreach (DirectoryInfo di in directoryInfos)
                {
                    this.ProcessDirectory(di, subDirectoryDepth);
                }
            }
        }
    }
}
